<script setup lang="ts">
/**
 * 适用于一次性返回所有内容的场景
 */
import { onMounted, reactive, useTemplateRef, ref, nextTick } from 'vue';
import { marked } from 'marked';

const usingRef = useTemplateRef<HTMLDivElement>('using');
import xss from 'xss';
import mdStr from '../../../../../../README.md?raw';

const getLastNode = (node: ChildNode): ChildNode | null => {
    if (node.nodeType === Node.TEXT_NODE) {
        return node;
    }
    for (let i = node.childNodes.length; i >= 0; i--) {
        const childNode = node.childNodes[i];
        if (childNode) {
            const lastNode = getLastNode(childNode);
            if (lastNode) {
                return lastNode;
            }
        }
    }
    return null;
};
const cursorPosition = reactive({
    x: 0,
    y: 0,
});

const showCursor = ref(false);

const updateCursorPosition = () => {
    const htmlContainer = usingRef.value!.querySelector(
        '.html-container',
    ) as HTMLDivElement;
    // 这个节点的 textContent 的末尾可能包含 |
    const lastNode = getLastNode(htmlContainer);
    if (lastNode && lastNode.toString() != '') {
        const range = document.createRange();
        // 框选 '|'
        if (lastNode.textContent?.length === 1) {
            range.setStart(lastNode, 0);
            range.setEnd(lastNode, 0);
        } else {
            const lastCharIndex = lastNode.toString().length - 1;
            range.setStart(lastNode, lastCharIndex - 1);
            range.setEnd(lastNode, lastCharIndex);
        }

        // 获取文本节点的位置
        const rect = range.getBoundingClientRect();
        cursorPosition.x = rect.left;
        cursorPosition.y = rect.top;

        // 删除 textContent 末尾的 |
        if (lastNode.textContent?.length === 1) {
            lastNode.textContent = '';
        } else {
            const lastCharIndex = lastNode.toString().length - 1;
            lastNode.textContent = lastNode
                .toString()
                .substring(0, lastCharIndex);
        }
    }
};

onMounted(() => {
    speak(mdStr);
});

const scrollToBottom = () => {
    const htmlContainer = usingRef.value;
    if (htmlContainer) {
        htmlContainer.scrollTop = htmlContainer.scrollHeight;
    }
};

const htmlStr = ref('');
let timer: NodeJS.Timeout;
const speak = (mdStr: string) => {
    let wordIndex = 0;
    const nextWord = () => {
        clearTimeout(timer);
        timer = setTimeout(() => {
            wordIndex += Math.ceil(Math.random() * 10); // 这个决定了每一小段的长度
            htmlStr.value = mdToHtml(
                // + '|' 是为了做光标
                mdStr.slice(0, wordIndex) + '|',
            );

            // 等 htmlStr 在页面上渲染出来
            nextTick(() => {
                scrollToBottom();
                showCursor.value = true;
                updateCursorPosition();

                if (wordIndex >= mdStr.length) {
                    showCursor.value = false;
                    return;
                }
                nextWord();
            });
        }, Math.random() * 300);
    };
    nextWord();
};

const mdToHtml = (md: string) => {
    return marked.parse(md, { async: false });
};
</script>

<template>
    <div class="using" ref="using">
        <!-- 滚动条是属于 using 的 -->
        <div class="html-container" v-html="xss(htmlStr)" />
        <div
            v-show="showCursor"
            class="cursor"
            :style="{
                left: `${cursorPosition.x}px`,
                top: `${cursorPosition.y}px`,
            }"
        >
            |
        </div>
    </div>
</template>

<style scoped lang="scss">
.using {
    position: relative;
    height: 400px;
    overflow-y: auto;
    box-sizing: border-box;
    white-space: pre-wrap;

    .cursor {
        position: fixed;
        color: #000;
        font-size: 20px;
        animation: cursor-blink 0.5s infinite;
        @keyframes cursor-blink {
            0%,
            100% {
                opacity: 1;
            }
            50% {
                opacity: 0;
            }
        }
    }
}
</style>
